﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Emit = System.Reflection.Emit;
using System.Reflection;
using System.Threading;

namespace CYTS.Aoyou.Framework.Core
{
    /// <summary>
    /// 类构造器基类
    /// </summary>
    /// <typeparam name="TType"></typeparam>
    public abstract class TypeGeneratorBase<TType> : ITypeGenerator where TType : class
    {
        private const string ASSEMBLY_NAME = "CYTS.Aoyou.Framework.DynamicType.";
        private static bool saveGeneratedAssembly = false; // 设置成true即可把动态生成的程序集存到磁盘上
        private Emit.AssemblyBuilder assemblyBuilder;
        private AssemblyName assemblyName;
        protected const MethodAttributes defaultMethodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot;
        protected Emit.ModuleBuilder moduleBuilder;
        protected readonly Type baseClassType;
        protected string generatedAssemblyName;
        protected string generatedClassName;

        protected TypeGeneratorBase(Type baseClassType)
        {
            this.baseClassType = baseClassType;
        }

        /// <summary>
        /// 生成程序集
        /// </summary>
        protected void GenerateAssembly()
        {
            try
            {
                if (assemblyBuilder == null)
                {
                    assemblyName = new AssemblyName();
                    assemblyName.Name = generatedAssemblyName;
                    assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, saveGeneratedAssembly ? Emit.AssemblyBuilderAccess.RunAndSave : Emit.AssemblyBuilderAccess.Run);
                    if (saveGeneratedAssembly)
                    {
                        moduleBuilder = assemblyBuilder.DefineDynamicModule(generatedAssemblyName, generatedAssemblyName + ".dll");
                    }
                    else
                    {
                        moduleBuilder = assemblyBuilder.DefineDynamicModule(generatedAssemblyName);
                    }
                }

                if (moduleBuilder == null)
                {
                    throw new Exception("未生成module builder");
                }
            }
            catch (Exception ex)
            {
                throw new Exception("未生成程序集 " + generatedAssemblyName, ex);
            }
        }

        /// <summary>
        /// 生成类的实现
        /// </summary>
        /// <returns></returns>
        protected virtual Type GenerateTypeImplementation()
        {
            Emit.TypeBuilder builder;
            try
            {
                builder = moduleBuilder.DefineType(generatedAssemblyName + "." + generatedClassName, TypeAttributes.Public, baseClassType,
                        new Type[] { });
            }
            catch (Exception ex)
            {
                throw new Exception("未能定义类型 " + generatedClassName + " : " + baseClassType, ex);
            }
            if (builder == null)
            {
                throw new Exception("未能定义类型 " + generatedClassName + " : " + baseClassType);
            }

            GenerateConstructor(builder);
            GenerateMethodImpl(builder);

            try
            {
                Type generatedType = builder.CreateType();
                return generatedType;
            }
            catch (Exception ex)
            {
                throw new Exception("未能创建类型 " + generatedClassName + " : " + baseClassType, ex);
            }
        }

        /// <summary>
        /// 保存程序集到磁盘
        /// </summary>
        protected void SaveGeneratedAssembly()
        {
            if (saveGeneratedAssembly)
            {
                assemblyBuilder.Save(generatedAssemblyName + ".dll");
                assemblyBuilder = null; // 重置
            }
        }

        /// <summary>
        /// 生成类中方法的实现
        /// </summary>
        /// <param name="builder"></param>
        protected abstract void GenerateMethodImpl(Emit.TypeBuilder builder);

        /// <summary>
        /// 生成类的构造函数
        /// </summary>
        /// <param name="builder"></param>
        protected virtual void GenerateConstructor(Emit.TypeBuilder builder)
        {
            ConstructorInfo[] originalConstructors = baseClassType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
            foreach (var constructor in originalConstructors)
            {
                var parameters = constructor.GetParameters();
                if (parameters.Length > 0 && parameters.Last().IsDefined(typeof(ParamArrayAttribute), false))
                {
                    continue;
                }
                Type[] constructorParameters = parameters.Select(p => p.ParameterType).ToArray();
                Emit.ConstructorBuilder constructorBuilder = builder.DefineConstructor(MethodAttributes.Public | MethodAttributes.RTSpecialName, CallingConventions.Standard, constructorParameters);
                Emit.ILGenerator iLGenerator = constructorBuilder.GetILGenerator();
                iLGenerator.Emit(Emit.OpCodes.Ldarg_0);
                for (var i = 1; i <= parameters.Length; ++i)
                {
                    iLGenerator.Emit(Emit.OpCodes.Ldarg, i);
                }

                iLGenerator.Emit(Emit.OpCodes.Call, constructor);
                iLGenerator.Emit(Emit.OpCodes.Ret);
            }
        }

        /// <summary>
        /// 获取方法的入参类型
        /// </summary>
        /// <param name="declareParams"></param>
        /// <returns></returns>
        protected Type[] GetParameters(ParameterInfo[] declareParams)
        {
            List<Type> parameters = new List<Type>();
            foreach (ParameterInfo param in declareParams)
            {
                parameters.Add(param.ParameterType);
            }
            return parameters.ToArray();
        }

        /// <summary>
        /// 构造类型
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        public Type GenerateType(string className)
        {
            generatedClassName = className;
            generatedAssemblyName = ASSEMBLY_NAME + generatedClassName;
            GenerateAssembly();
            Type type = moduleBuilder.GetType(generatedClassName);
            if (type != null)
            {
                return type;
            }

            type = GenerateTypeImplementation();
            SaveGeneratedAssembly();
            return type;
        }
    }
}
